热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

可能会|前文_c++虚表学习2

篇首语:本文由编程笔记#小编为大家整理,主要介绍了c++虚表学习2相关的知识,希望对你有一定的参考价值。 前文 本文会让读者明白虚表原理,理解类的大致内存结构。棱形继承下内存布局&#xf

篇首语:本文由编程笔记#小编为大家整理,主要介绍了c++虚表学习2相关的知识,希望对你有一定的参考价值。



前文
  1. 本文会让读者明白虚表原理,
  2. 理解类的大致内存结构。棱形继承下内存布局,和虚继承单一内存布局情况。
  3. 父类构造函数调用虚函数会怎么样
  4. 父类析构函数调用虚函数情况
  5. 为什么析构函数一定要是虚函数

单继承

我们首先参阅如下的代码:

class Person
public:
int pFlag = 2;
Person()
printf("Person \\r\\n");

virtual ~Person()
printf(" ~Person \\r\\n");

virtual void vSayPerson()
printf("Person \\r\\n");

void nSayPerson()
printf("nSayPerson \\r\\n");

;
class XH :public Person
public:
int xhFlag = 4;
virtual ~XH()
printf("~XH \\r\\n");

XH()
printf("XH \\r\\n");

virtual void vSayXH()
printf("vSayXH \\r\\n");

virtual void vSayXH2()
printf("vSayXH2 \\r\\n");

void nSayXH()
printf("nSayXH \\r\\n");

;
int main()


XH* pXh = new XH();
pXh->nSayPerson();
pXh->vSayPerson();
pXh->vSayXH2();
pXh->nSayXH();
delete pXh;


return 0;

Debug编译后的汇编代码


我们跟进到构造函数中:

我们按照上面的顺序逐个分析


  1. 调用父类的构造函数。因为子类可能会用到父类的东西
  2. 给自己虚表赋值,注意虚表在内存首地址。
  3. 给自己变量赋值 。在上面你注意 [eax+8] 这个地址不是eax+4,证明中间还有其他东西。这里存放的是父类的局部变量
  4. 调用自身构造方法内的函数体

我们首先构建出整个内存图:

构造函数要先调父类的初始化函数:
因为子类会用到父类资源,比如子类获取父类的变量

先初始化虚表指针在调用属性初始化和方法体:
因为构造函数会有可能调用虚函数

先初始化属性在调用方法体:
方法体可能会获取属性

我们首先虚表的赋值代码:

我们接下来看下Person这个类的初始化函数



你会差异的发现父类构造也会填入自己虚表,完成父类构造的后,子类又会覆盖写入这个虚表地址。
这样会有什么关系和异常呢?假设子类重写父类的虚函数,在父类构造函数调用虚函数只会调用自己的函数而不是子类的。

我们看下XH虚表地址的交叉引用信息

我们观察下XH析构函数:

为啥需要在自己析构函数中再次给自己的虚表赋值呢?为了解答这个答案我们首先才看~Person析构


在析构对象流程,首先释放子类的所有子类资源,在释放父类所有资源。因为子类资源被释放了,如果调用到父类时虚表没有还原父类的虚表,那么父类析构中有调用虚函数的可能会引起意外的异常。因为指向的函数是一个释放资源的子类函数。

我们最后看看几个虚函数和非虚函数的调用

pXh->nSayPerson();
pXh->vSayPerson();


首先我们要知道的是XH的虚表中第二项就是vSayPerson函数地址,第一项是析构函数代理函数地址。

现在你应该对虚函数的调用有一定的认识了吧。现在你应该举一反三回答出为啥析构函数为啥一定要是虚函数了。。。

我们现在重写Person虚函数看看XH的虚表会怎么样.


class XH :public Person
public:
int xhFlag = 4;
virtual ~XH()
printf("~XH \\r\\n");

XH()
printf("XH \\r\\n");

virtual void vSayXH()
printf("vSayXH \\r\\n");

virtual void vSayXH2()
printf("vSayXH2 \\r\\n");

void nSayXH()
printf("nSayXH \\r\\n");

//重写
virtual void vSayPerson()
printf("XH vSayPerson\\r\\n");

;


多继承

class BaseClass
public:
int baseFlag = -1;
int fill[32] = 0;
BaseClass(int flag)
baseFlag = flag;
printf("BaseClass \\r\\n");

virtual ~BaseClass()
printf(" ~BaseClass \\r\\n");

;
class Person : public BaseClass
public:
int pFlag = 2;
Person() :BaseClass(2)
printf("Person \\r\\n");

virtual ~Person()
printf(" ~Person \\r\\n");

virtual void vSayPerson()
printf("Person \\r\\n");

void nSayPerson()
printf("nSayPerson \\r\\n");

;
class Female : public BaseClass
public:
int fFlag = 3;
Female() :BaseClass(3)
printf("Female \\r\\n");

virtual ~Female()
printf(" ~Female \\r\\n");

virtual void vSayFemale()
printf("vSayFemale \\r\\n");

void nSayFemale()
printf("nSayFemale \\r\\n");

;

class XM :public Person, public Female
public:
virtual void vSayFemale()
printf("vSayFemale \\r\\n");

virtual ~XM()
printf("~XM \\r\\n");

XM()
printf("XM \\r\\n");

virtual void sayXm()
printf("sayXm \\r\\n");

;

int main()

XM* pXm = new XM();
//280
printf("%d \\r\\n", sizeof XM);
delete pXm;
return 0;


我们可以看到多继承下XM输出类大小是280字节,我们改用虚继承

class BaseClass
public:
int baseFlag = -1;
int fill[32] = 0;
BaseClass(int flag)
baseFlag = flag;
printf("BaseClass \\r\\n");

virtual ~BaseClass()
printf(" ~BaseClass \\r\\n");

;
class Person : virtual public BaseClass
public:
int pFlag = 2;
Person() :BaseClass(2)
printf("Person \\r\\n");

virtual ~Person()
printf(" ~Person \\r\\n");

virtual void vSayPerson()
printf("Person \\r\\n");

void nSayPerson()
printf("nSayPerson \\r\\n");

;
class Female :virtual public BaseClass
public:
int fFlag = 3;
Female() :BaseClass(3)
printf("Female \\r\\n");

virtual ~Female()
printf(" ~Female \\r\\n");

virtual void vSayFemale()
printf("vSayFemale \\r\\n");

void nSayFemale()
printf("nSayFemale \\r\\n");

;

class XM :public Person, public Female
public:
virtual void vSayFemale()
printf("vSayFemale \\r\\n");

virtual ~XM()
printf("~XM \\r\\n");

XM():BaseClass(0x123)
printf("XM \\r\\n");

virtual void sayXm()
printf("sayXm \\r\\n");

;

int main()

XM* pXm = new XM();
//280
printf("%d \\r\\n", sizeof XM);
delete pXm;
return 0;


普通多继承类内存视图

可见虚继承减少部分内存,我们首先研究下分非虚继承下的XM内存结构

我们直接查看XM的构造函数

析构方法


多继承类内存视图

我们知道虚继承可减少共同父类占用空间,比如本例中XM类会有两个BaseClass类,因此我们会想280减去一个Base类大小就是虚继承后的大小。具体数值为 144=280-136,但是我们通过允许后发现实际内存是160大小。

//..其他代码略,这里是虚继承的代码
int main()

XM* pXm = new XM();
printf("sizeof XM %d \\r\\n", sizeof XM);
printf("sizeof Female %d \\r\\n", sizeof Female);
printf("sizeof Person %d \\r\\n", sizeof Person);
printf("sizeof BaseClass %d \\r\\n", sizeof BaseClass);
delete pXm;
return 0;


我们很明显发现FemalePerson大小变大了8字节.

为了研究这个问题们修改以下代码首先构造一个Female


int main()

Female* pXm = new Female();
printf("sizeof XM %d \\r\\n", sizeof XM);
printf("sizeof Female %d \\r\\n", sizeof Female);
printf("sizeof Person %d \\r\\n", sizeof Person);
printf("sizeof BaseClass %d \\r\\n", sizeof BaseClass);
delete pXm;
return 0;

我们这里为方便理解直接给出虚继承内存结构:

IDA PRO 查看构造函数你会发现这个代码会有分支结构,有可能不会调用虚基类初始化函数


我们最后再看看XM这个类的内存结构体:



推荐阅读
  • 深入解析:Synchronized 关键字在 Java 中对 int 和 Integer 对象的作用与影响
    深入探讨了 `Synchronized` 关键字在 Java 中对 `int` 和 `Integer` 对象的影响。尽管初看此题似乎简单,但其实质在于理解对象的概念。根据《Java编程思想》第二章的观点,一切皆为对象。本文详细分析了 `Synchronized` 关键字在不同数据类型上的作用机制,特别是对基本数据类型 `int` 和包装类 `Integer` 的区别处理,帮助读者深入理解 Java 中的同步机制及其在多线程环境中的应用。 ... [详细]
  • [转]doc,ppt,xls文件格式转PDF格式http:blog.csdn.netlee353086articledetails7920355确实好用。需要注意的是#import ... [详细]
  • 在本文中,我们将为 HelloWorld 项目添加视图组件,以确保控制器返回的视图路径能够正确映射到指定页面。这一步骤将为后续的测试和开发奠定基础。首先,我们将介绍如何配置视图解析器,以便 SpringMVC 能够识别并渲染相应的视图文件。 ... [详细]
  • AIX编程挑战赛:AIX正方形问题的算法解析与Java代码实现
    在昨晚的阅读中,我注意到了CSDN博主西部阿呆-小草屋发表的一篇文章《AIX程序设计大赛——AIX正方形问题》。该文详细阐述了AIX正方形问题的背景,并提供了一种基于Java语言的解决方案。本文将深入解析这一算法的核心思想,并展示具体的Java代码实现,旨在为参赛者和编程爱好者提供有价值的参考。 ... [详细]
  • 本文介绍了在 Java 编程中遇到的一个常见错误:对象无法转换为 long 类型,并提供了详细的解决方案。 ... [详细]
  • 本文回顾了作者初次接触Unicode编码时的经历,并详细探讨了ASCII、ANSI、GB2312、UNICODE以及UTF-8和UTF-16编码的区别和应用场景。通过实例分析,帮助读者更好地理解和使用这些编码。 ... [详细]
  • 单片微机原理P3:80C51外部拓展系统
      外部拓展其实是个相对来说很好玩的章节,可以真正开始用单片机写程序了,比较重要的是外部存储器拓展,81C55拓展,矩阵键盘,动态显示,DAC和ADC。0.IO接口电路概念与存 ... [详细]
  • 本文对比了杜甫《喜晴》的两种英文翻译版本:a. Pleased with Sunny Weather 和 b. Rejoicing in Clearing Weather。a 版由 alexcwlin 翻译并经 Adam Lam 编辑,b 版则由哈佛大学的宇文所安教授 (Prof. Stephen Owen) 翻译。 ... [详细]
  • 本文将详细介绍Python中*args和**kwargs的用法,包括它们的基本概念、应用场景以及注意事项。 ... [详细]
  • 文章目录Golang定时器Timer和Tickertime.Timertime.NewTimer()实例time.AfterFunctime.Tickertime.NewTicke ... [详细]
  • ARM汇编基础基于Keil创建STM32汇编程序的编写
    文章目录一、新建项目(1)工具介绍(2)创建项目:二、配置环境(1)配置芯片&#x ... [详细]
  • 本题探讨如何编写程序来计算一个数值的整数次方,涉及多种情况的处理。 ... [详细]
  • 单片机入门指南:基础理论与实践
    本文介绍了单片机的基础知识及其应用。单片机是一种将微处理器(类似于CPU)、存储器(类似硬盘和内存)以及多种输入输出接口集成在一块硅片上的微型计算机系统。通过详细解析其内部结构和功能,帮助初学者快速掌握单片机的基本原理和实际操作方法。 ... [详细]
  • 本文介绍了如何通过HTML样式为网页标签添加各种视觉效果。通过使用CSS类和内联样式,开发者可以轻松地控制文本颜色、背景色、边框、字体大小等属性,从而提升网页的美观性和用户体验。示例代码展示了如何在HTML标签中应用这些样式,以便开发者能够快速上手并应用于实际项目中。 ... [详细]
  • 基于OpenCV的图像拼接技术实践与示例代码解析
    图像拼接技术在全景摄影中具有广泛应用,如手机全景拍摄功能,通过将多张照片根据其关联信息合成为一张完整图像。本文详细探讨了使用Python和OpenCV库实现图像拼接的具体方法,并提供了示例代码解析,帮助读者深入理解该技术的实现过程。 ... [详细]
author-avatar
Missluckyyy_879
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有